iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
AI Engineering

從零訓練到瀏覽器部署:30 天打造 Atari Breakout 強化學習 AI系列 第 24

Day 24|模型一次決策要多久?我才發現 GPU Benchmark 其實很容易量錯

  • 分享至 

  • xImage
  •  

Day 23 已經確認一件很重要的事:把模型從 PyTorch 換成 ONNX Runtime 之後,Agent 的決策沒有跑掉。

接下來自然會想問:

那它到底跑得夠不夠快?

如果要讓 Agent 一邊玩 Breakout、一邊根據畫面做決策,那每次判斷就不能拖太久。

它每拿到一個新的遊戲狀態,大致要做四件事:

  1. 把畫面整理成模型能讀的資料;
  2. 讓模型算出四個動作的分數;
  3. 選出分數最高的動作;
  4. 把動作交回遊戲。

所以 Day 24 不再問「模型會不會玩」,而是問另一個很實際的問題:

Agent 每做一次決策,到底要花多久?


為什麼這次特別看 Batch=1?

很多 AI Benchmark 喜歡一次丟很多資料給 GPU,因為這樣通常比較容易跑出漂亮的效能數字。

但玩 Breakout 的 Agent 不是這樣工作。

它的節奏比較像:

看到現在的畫面
→ 做一次決策
→ 遊戲往前走
→ 再看新的畫面
→ 再做一次決策

所以它大部分時間一次只需要處理一個遊戲狀態

這就是 batch=1

如果拿一次處理 32 個狀態的結果,去代表實際玩遊戲時一次只處理 1 個狀態的速度,很容易得到錯誤的印象。

因此 Day 24 的主要結果全部以 batch=1 為主。


但「量模型速度」其實很容易量錯

一開始我也以為 Benchmark 很簡單:

開始計時
model(input)
停止計時

但真的做到 GPU 才發現,事情沒這麼單純。

第一個坑:第一次通常特別慢

模型第一次執行時,常常還要先做一些準備工作。

所以如果只跑一次:

第一次 = 5 ms

不能直接說:

我的模型每次都要 5 ms。

這次正式測量前,我先跑 25 次 warm-up,也就是先讓模型跑幾次進入穩定狀態;這些結果完全不進統計,之後才記錄 100 次正式測量。


第二個坑:GPU 很可能還沒算完,Python 就往下跑了

GPU 很多工作是非同步的。

簡單來說,Python 把工作交給 GPU 之後,不一定會站在原地等它算完。

如果這時候馬上停止計時,很可能只量到:

「把工作交出去花多久」

而不是:

「GPU 真正把模型算完花多久」

所以這次測 GPU 時,會在必要的位置等它真的完成工作,再讀取時間。

不然很容易得到一個看起來快得不可思議、實際上卻沒有意義的數字。


我不只看平均值,而是看 P50 和 P95

假設測了 100 次:

大部分:1 ms
偶爾:4 ms

只看平均值,很容易把偶發的慢延遲藏掉。

所以這次主要看兩個數字:

  • P50:可以把它理解成平常最常遇到的速度;
  • P95:可以用來看偶爾比較慢的情況。

對即時操作來說,P95 很重要。

因為真正讓人感覺「卡」的,往往不是平均速度,而是偶爾突然慢一下。


到底要量「模型本身」還是「一次完整決策」?

這次我把速度拆成兩種看法。

只量模型

先把模型要吃的資料準備好,再開始計時。

這回答的是:

資料都準備好了之後,模型本身算一次需要多久?

而且這次我把資料格式、大小和數值是否合法的檢查移到計時外,避免把額外的 Python 檢查也算進模型速度。

完整決策

這個才更接近 Agent 實際玩遊戲時會經過的流程。

它從原本的遊戲畫面開始,一路包含:

遊戲畫面
→ 整理成模型輸入
→ 模型計算
→ 拿到四個動作分數
→ 選出下一個動作

所以如果要問:

「Agent 每做一次決策,實際要付出多少時間?」

我會優先看完整決策,而不是只看最漂亮的「模型本身」數字。


實測結果:一次決策其實比我預期快很多

正式測試比較了四種方式:

  • PyTorch CPU
  • PyTorch CUDA
  • ONNX Runtime CPU
  • ONNX Runtime CUDA

下面這張圖直接來自實際 benchmark 資料,會同時畫出 P50 和 P95。

Day 24 batch=1 推論延遲比較

這次正式 v2 run 的 batch=1 完整決策結果如下:

執行方式 P50 P95
PyTorch CPU 2.533 ms 3.298 ms
PyTorch CUDA 1.526 ms 2.862 ms
ONNX Runtime CPU 0.856 ms 1.596 ms
ONNX Runtime CUDA 1.342 ms 2.663 ms

這裡最值得注意的,不是哪一個名字排第一,而是:四種方式都只有幾毫秒。

另外,這一輪 ONNX Runtime CPU 甚至比 CUDA 更快一些。這不代表 CPU 永遠比較強,而是提醒我們:模型很小、一次只算一個狀態時,GPU 額外要付出的資料搬移和等待成本,也可能變得很明顯。

所以這組結果比較適合拿來回答:

模型算一次到底快不快?

而不是拿來宣布某一種執行方式永遠最快。


為什麼我還要看 100 次延遲分布?

只看上面的表格還是不夠。

例如兩種方式都可能是:

P50 = 1 ms

但其中一種非常穩定,另一種可能偶爾突然跳到 5 ms。

所以第二張圖直接把 100 次 batch=1 完整決策的測量畫出來。

Day 24 batch=1 延遲分布

看這張圖時可以注意兩件事:

  • 柱子主要集中在哪裡:代表大部分決策平常花多久;
  • P50 和 P95 距離多遠:距離越大,代表偶爾慢下來的情況越明顯。

這比拿一次最快結果說「我的模型只要 0.x ms」誠實得多。


GPU 並不是任何情況都一定碾壓 CPU

這次還有一個很有意思的現象。

這顆 DQN 其實不算大。

在這種小模型、batch=1 的情況下,真正的神經網路計算本來就很短;這時候資料搬到 GPU、等待 GPU 完成、再把結果拿回來,反而可能占掉不小的比例。

所以不能直接套用:

GPU 一定比 CPU 快很多。

如果只看模型本身,batch=1 的 P50 是:

執行方式 只量模型 P50
PyTorch CPU 0.989 ms
PyTorch CUDA 0.625 ms
ONNX Runtime CPU 0.221 ms
ONNX Runtime CUDA 0.306 ms

但把資料整理、模型計算和最後選動作全部算進去後,順序又會改變。

這就是為什麼我會把「模型本身」和「一次完整決策」分開看。


CPU 的執行緒也不是開越多越快

CPU 測試另外比較了不同執行緒數量。

最新結果裡,PyTorch CPU 使用 2 個執行緒時,P50 / P95 是 1.424 / 1.933 ms;使用預設的 12 個執行緒時,反而變成 2.533 / 3.298 ms

ONNX Runtime CPU 也沒有出現「執行緒越多就越快」的單純規律。

這件事很反直覺,但對小模型並不奇怪。

因為多開執行緒本身也需要協調,工作太小時,那些額外成本反而可能拖慢速度。

所以如果以後看到:

CPU A 比 CPU B 快

最好先問一句:

兩邊用了幾個執行緒?

不然很容易把不同設定下的結果直接拿來比較。


那這樣到底夠不夠快?

可以先用一個很粗略的尺度來想。

如果遊戲每秒更新大約 60 張畫面,那一張畫面的時間大約是:

1000 / 60 ≈ 16.7 ms

這個 Agent 不是每一張畫面都重新做決策,而是隔幾張畫面才決定一次。以四張畫面當作簡單的比較尺度,大約是:

16.7 × 4 ≈ 66.7 ms

而這次四種完整決策的 P95 都只有:

約 1.6 ~ 3.3 ms

兩邊不是同一件事,所以不能把 66.7 - 3.3 當成真正剩下的「可用時間」。但至少可以看出一件事:

目前模型做一次決策,只占很小的一段時間。

所以現在沒有必要因為「怕模型太慢」就急著把 CNN 改小。

這是 Day 24 真正能得到的結論。


今天還不能回答什麼?

今天量到的是:

模型在這台電腦上做一次決策要多久。

它沒有把整個遊戲從畫面更新、讀取狀態、模型判斷,到最後顯示畫面的所有時間都一起量進去。

所以今天還不能直接說:

整個遊戲一定會很順。

因為完整遊戲還有其他工作要做,例如:

  • 遊戲本身往前跑;
  • 把新畫面整理成模型能讀的資料;
  • 把模型算出的動作交回遊戲;
  • 把畫面顯示出來。

這些都不是 Day 24 今天要量的範圍。

也就是說,Day 24 比較像是在先排除一個可能性:

至少目前沒有看到「模型本身太慢」這個問題。

至於整套程式跑起來之後到底順不順,必須等真的把完整流程接起來再測。


小結

今天我最大的收穫反而不是「哪個方式最快」,而是發現 Benchmark 本身也需要被設計。

要讓速度數字有意義,至少要注意:

  • 測真正的使用情境,所以這次以 batch=1 為主;
  • 第一次執行不算正式結果,要先 warm-up;
  • GPU 要等它真的算完,不能只量到把工作交出去;
  • 不只看平均值,也看 P50 / P95 和完整分布;
  • 把「模型本身」和「一次完整決策」分開。

目前的結果告訴我:模型本身還有很大的速度餘裕。

那下一個問題就變成:

如果把 FP32 換成 FP16,能不能再快一點?而且會不會因此讓 Agent 的輸出跑掉?

這就是 Day 25 要做的事。


上一篇
Day 23|換成 ONNX Runtime 之後,它還是同一個 Agent 嗎?
下一篇
Day 25|FP16 真的比較快嗎?模型縮小一半,我最後卻決定不用它
系列文
從零訓練到瀏覽器部署:30 天打造 Atari Breakout 強化學習 AI27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言